﻿/////////////////////////////////////////////////////
//
// 		程	序	名 	： 	基于 SDF 与法线贴图的 PBR 光照渲染
//		作  	者 	：	Margoo
//		邮		箱	：	qiuzhengyu@siggraph.org
//

#include <cmath>
#include <ctime>
#include <format>
#include <graphics.h>
#include <vector>

#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>

int Map[5][5] = {
	{0, 0, 0, 0, 0}, {0, 1, 0, 2, 0}, {0, 0, 0, 0, 0}, {0, 0, 3, 0, 0}, {0, 0, 0, 0, 0},
};

constexpr int BlockRadius = 128;

/**
 * 简单的二维向量结构体
 */
struct Vec2 {
	float X;
	float Y;
};

IMAGE *CobbledDeepslate;
IMAGE *DiamondBlock;
IMAGE *EmeraldBlock;
IMAGE *RedstoneBlock;
IMAGE *CobbledDeepslateNormal;
IMAGE *DiamondBlockNormal;
IMAGE *EmeraldBlockNormal;
IMAGE *RedstoneBlockNormal;

/**
 * 渲染由基础贴图组成的地图
 * @return 一个指向基本地图图像的指针
 */
[[nodiscard("Memory Leaking")]] IMAGE *GeneratingMap() {
	auto map = new IMAGE(BlockRadius * 5, BlockRadius * 5);
	SetWorkingImage(map);

	for (int y = 0; y < 5; ++y) {
		for (int x = 0; x < 5; ++x) {
			switch (Map[y][x]) {
			case 0: {
				putimage(x * BlockRadius, y * BlockRadius, CobbledDeepslate);

				break;
			}
			case 1: {
				putimage(x * BlockRadius, y * BlockRadius, DiamondBlock);

				break;
			}
			case 2: {
				putimage(x * BlockRadius, y * BlockRadius, EmeraldBlock);

				break;
			}
			case 3: {
				putimage(x * BlockRadius, y * BlockRadius, RedstoneBlock);

				break;
			}

			default: {
				break;
			}
			}
		}
	}

	SetWorkingImage();

	return map;
}
/**
 * 渲染由法线贴图组成的地图
 * @return 一个指向法线地图图像的指针
 */
[[nodiscard("Memory Leaking")]] IMAGE *GeneratingNormalMap() {
	auto map = new IMAGE(BlockRadius * 5, BlockRadius * 5);
	SetWorkingImage(map);

	for (int y = 0; y < 5; ++y) {
		for (int x = 0; x < 5; ++x) {
			switch (Map[y][x]) {
			case 0: {
				putimage(x * BlockRadius, y * BlockRadius, CobbledDeepslateNormal);

				break;
			}
			case 1: {
				putimage(x * BlockRadius, y * BlockRadius, DiamondBlockNormal);

				break;
			}
			case 2: {
				putimage(x * BlockRadius, y * BlockRadius, EmeraldBlockNormal);

				break;
			}
			case 3: {
				putimage(x * BlockRadius, y * BlockRadius, RedstoneBlockNormal);

				break;
			}

			default: {
				break;
			}
			}
		}
	}

	SetWorkingImage();

	return map;
}

/**
 * 混合两个 IMAGE 对象，生成新的 IMAGE 对象；传入的两个图片对象必须具有相同的
 * 宽高
 * @param A 对象 A
 * @param B 对象 B
 * @return 一个指向对象 A 与 B 混合结果图像的指针
 */
[[nodiscard("Memory Leaking")]] IMAGE *Blend(IMAGE *A, IMAGE *B) {
	auto blendImage = new IMAGE(A->getwidth(), A->getheight());
	auto buffer		= GetImageBuffer(blendImage);
	auto ABuffer	= GetImageBuffer(A);
	auto BBuffer	= GetImageBuffer(B);

	const int bufferSize = A->getwidth() * A->getheight();

	for (int x = 0; x < bufferSize; ++x) {
		auto A = ABuffer[x];
		auto B = BBuffer[x];

		// 让图像看起来更加明亮，故此处乘以 1.8
		auto r = GetRValue(A) * GetRValue(B) / 255 * 1.5;
		auto g = GetGValue(A) * GetGValue(B) / 255 * 1.5;
		auto b = GetBValue(A) * GetBValue(B) / 255 * 1.5;

		r = r > 255 ? 255 : r;
		g = g > 255 ? 255 : g;
		b = b > 255 ? 255 : b;

		buffer[x] = RGB(r, g, b);
	}

	return blendImage;
}
/**
 * 混合两个 IMAGE 对象并输出到指定对象上；传入的两个图片对象必须具有相同的宽高
 * @param A 对象 A
 * @param B 对象 B
 * @param Output 输出对象
 */
void BlendOn(IMAGE *A, IMAGE *B, IMAGE *Output) {
	auto buffer	 = GetImageBuffer(Output);
	auto ABuffer = GetImageBuffer(A);
	auto BBuffer = GetImageBuffer(B);

	const int bufferSize = A->getwidth() * A->getheight();

	for (int x = 0; x < bufferSize; ++x) {
		auto A = ABuffer[x];
		auto B = BBuffer[x];

		// 让图像看起来更加明亮，故此处乘以 1.5
		auto r = GetRValue(A) * GetRValue(B) / 255 * 1.5;
		auto g = GetGValue(A) * GetGValue(B) / 255 * 1.5;
		auto b = GetBValue(A) * GetBValue(B) / 255 * 1.5;

		r = r > 255 ? 255 : r;
		g = g > 255 ? 255 : g;
		b = b > 255 ? 255 : b;

		buffer[x] = RGB(r, g, b);
	}
}
/**
 * 将对象 A 累加至 B 对象指定的位置处
 * @param A 对象 A
 * @param B 目标对象 B
 * @param IX 目标 X
 * @param IX 目标 Y
 */
void SumOn(IMAGE *A, IMAGE *B, const float &IX, const float &IY) {
	auto buffer	 = GetImageBuffer(B);
	auto ABuffer = GetImageBuffer(A);

	int X = int(IX);
	int Y = int(IY);

	const int bufferSize = A->getwidth() * A->getheight();

	for (int y = 0; y < A->getheight(); ++y) {
		for (int x = 0; x < A->getwidth(); ++x) {
			auto AC = ABuffer[y * A->getwidth() + x];
			auto BC = buffer[(y + Y) * B->getwidth() + (x + X)];

			auto r = GetRValue(AC) + GetRValue(BC);
			auto g = GetGValue(AC) + GetGValue(BC);
			auto b = GetBValue(AC) + GetBValue(BC);

			r = r > 255 ? 255 : r;
			g = g > 255 ? 255 : g;
			b = b > 255 ? 255 : b;

			buffer[(y + Y) * B->getwidth() + (x + X)] = RGB(r, g, b);
		}
	}
}

/**
 * 初始化资源
 */
void InitAssets() {
	CobbledDeepslate	   = new IMAGE;
	DiamondBlock		   = new IMAGE;
	EmeraldBlock		   = new IMAGE;
	RedstoneBlock		   = new IMAGE;
	CobbledDeepslateNormal = new IMAGE;
	DiamondBlockNormal	   = new IMAGE;
	EmeraldBlockNormal	   = new IMAGE;
	RedstoneBlockNormal	   = new IMAGE;

	loadimage(CobbledDeepslate, _T("./assets/block/stone.png"));
	loadimage(CobbledDeepslateNormal, _T("./assets/block/stone_n.png"));
	loadimage(DiamondBlock, _T("./assets/block/diamond_block.png"));
	loadimage(DiamondBlockNormal, _T("./assets/block/diamond_block_n.png"));
	loadimage(EmeraldBlock, _T("./assets/block/emerald_block.png"));
	loadimage(EmeraldBlockNormal, _T("./assets/block/emerald_block_n.png"));
	loadimage(RedstoneBlock, _T("./assets/block/redstone_block.png"));
	loadimage(RedstoneBlockNormal, _T("./assets/block/redstone_block_n.png"));
}

/**
 * 圆的 SDF 函数
 * @param Point 目标点
 * @param Center 圆心
 * @param Radius 圆的半径
 * @return 目标点距离圆心的有符号距离
 */
float CircleSDF(const Vec2 &Point, const Vec2 &Center, const float &Radius) {
	auto x = Point.X - Center.X;
	auto y = Point.Y - Center.Y;

	return sqrt(pow(x, 2.f) + pow(y, 2.f)) - Radius;
}

/**
 * 高斯模糊相关算法实现
 */

std::vector<int> BoxForGass(double sigma, int box_number) {
	double wIdeal = sqrt((12 * sigma * sigma / box_number) + 1);
	double wl	  = floor(wIdeal);

	if (int(wl) % 2 == 0) {
		--wl;
	}

	double wu = wl + 2;

	double mIdeal = (12 * sigma * sigma - box_number * wl * wl - 4 * box_number * wl - 3 * box_number) / (-4 * wl - 4);
	int	   m	  = round(mIdeal);

	std::vector<int> sizes;

	for (int i = 0; i < box_number; ++i) {
		sizes.push_back(i < m ? wl : wu);
	}

	return sizes;
}
void GassBlurHeight(IMAGE *source_channel_image, IMAGE *target_channel_image, int width, int height, double radius) {
	auto source_channel = GetImageBuffer(source_channel_image);
	auto target_channel = GetImageBuffer(target_channel_image);

	double iArray = 1 / (radius + radius + 1);

	int ti;
	int li;
	int ri;

	int rfv;
	int rlv;

	int gfv;
	int glv;

	int bfv;
	int blv;

	int rval;
	int gval;
	int bval;

	for (int i = 0; i < height; ++i) {
		ti = i * width;
		li = ti;
		ri = ti + radius;

		rfv = GetRValue(source_channel[ti]);
		rlv = GetRValue(source_channel[ti + width - 1]);

		gfv = GetGValue(source_channel[ti]);
		glv = GetGValue(source_channel[ti + width - 1]);

		bfv = GetBValue(source_channel[ti]);
		blv = GetBValue(source_channel[ti + width - 1]);

		rval = (radius + 1) * rfv;
		gval = (radius + 1) * gfv;
		bval = (radius + 1) * bfv;

		for (int j = 0; j < radius; ++j) {
			rval += GetRValue(source_channel[ti + j]);
			gval += GetGValue(source_channel[ti + j]);
			bval += GetBValue(source_channel[ti + j]);
		}

		for (int j = 0; j <= radius; ++j) {
			rval += GetRValue(source_channel[ri]) - rfv;
			gval += GetGValue(source_channel[ri]) - gfv;
			bval += GetBValue(source_channel[ri]) - bfv;

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			++ri;
			++ti;
		}

		for (int j = radius + 1; j < width - radius; ++j) {
			rval += GetRValue(source_channel[ri]) - GetRValue(source_channel[li]);
			gval += GetGValue(source_channel[ri]) - GetGValue(source_channel[li]);
			bval += GetBValue(source_channel[ri]) - GetBValue(source_channel[li]);

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			++ri;
			++li;
			++ti;
		}

		for (int j = width - radius; j < width; ++j) {
			rval += rlv - GetRValue(source_channel[li]);
			gval += glv - GetGValue(source_channel[li]);
			bval += blv - GetBValue(source_channel[li]);

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			++li;
			++ti;
		}
	}
}
void GassBlurWidth(IMAGE *source_channel_image, IMAGE *target_channel_image, int width, int height, double radius) {
	auto source_channel = GetImageBuffer(source_channel_image);
	auto target_channel = GetImageBuffer(target_channel_image);

	double iArray = 1 / (radius + radius + 1);

	int ti;
	int li;
	int ri;

	int rfv;
	int rlv;

	int gfv;
	int glv;

	int bfv;
	int blv;

	int rval;
	int gval;
	int bval;

	for (int i = 0; i < width; ++i) {
		ti = i;
		li = ti;
		ri = ti + radius * width;

		rfv = GetRValue(source_channel[ti]);
		rlv = GetRValue(source_channel[ti + width * (height - 1)]);

		gfv = GetGValue(source_channel[ti]);
		glv = GetGValue(source_channel[ti + width * (height - 1)]);

		bfv = GetBValue(source_channel[ti]);
		blv = GetBValue(source_channel[ti + width * (height - 1)]);

		rval = (radius + 1) * rfv;
		gval = (radius + 1) * gfv;
		bval = (radius + 1) * bfv;

		for (int j = 0; j < radius; ++j) {
			rval += GetRValue(source_channel[ti + j * width]);
			gval += GetGValue(source_channel[ti + j * width]);
			bval += GetBValue(source_channel[ti + j * width]);
		}

		for (int j = 0; j <= radius; ++j) {
			rval += GetRValue(source_channel[ri]) - rfv;
			gval += GetGValue(source_channel[ri]) - gfv;
			bval += GetBValue(source_channel[ri]) - bfv;

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			ri += width;
			ti += width;
		}

		for (int j = radius + 1; j < height - radius; ++j) {
			rval += GetRValue(source_channel[ri]) - GetRValue(source_channel[li]);
			gval += GetGValue(source_channel[ri]) - GetGValue(source_channel[li]);
			bval += GetBValue(source_channel[ri]) - GetBValue(source_channel[li]);

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			li += width;
			ri += width;
			ti += width;
		}

		for (int j = height - radius; j < height; ++j) {
			rval += rlv - GetRValue(source_channel[li]);
			gval += glv - GetGValue(source_channel[li]);
			bval += blv - GetBValue(source_channel[li]);

			target_channel[ti] = RGB(int(round(rval * iArray)), int(round(gval * iArray)), int(round(bval * iArray)));

			li += width;
			ti += width;
		}
	}
}
void GassBlur(IMAGE *source_channel_image, IMAGE *target_channel_image, int width, int height, double radius) {
	int N = width * height;

	auto buffer		   = GetImageBuffer(source_channel_image);
	auto target_buffer = GetImageBuffer(target_channel_image);

	for (int i = 0; i < N; ++i) {
		target_buffer[i] = buffer[i];
	}

	GassBlurHeight(target_channel_image, source_channel_image, width, height, radius);
	GassBlurWidth(source_channel_image, target_channel_image, width, height, radius);
}
IMAGE *GassBlurFilter(IMAGE *input_image, int blur_radius) {
	IMAGE *output_image = new IMAGE(input_image->getwidth(), input_image->getheight());

	auto boxes = BoxForGass(blur_radius, 3);

	GassBlur(input_image, output_image, input_image->getwidth(), input_image->getheight(), (boxes[0] - 1) / 2);
	GassBlur(output_image, input_image, input_image->getwidth(), input_image->getheight(), (boxes[1] - 1) / 2);
	GassBlur(input_image, output_image, input_image->getwidth(), input_image->getheight(), (boxes[2] - 1) / 2);

	return output_image;
}

/**
 * 光照平滑函数
 */
float D(const float &X) {
	return pow(X, 1.f / 3.f);
}
/**
 * 生成光照蒙版
 * @return 光照蒙版贴图
 */
IMAGE *GeneratingLightMask(const float &Radius, const float &Range, const COLORREF &LColor) {
	auto  mask	 = new IMAGE(Range, Range);
	Vec2  center = {Range / 2, Range / 2};
	float r		 = GetRValue(LColor);
	float g		 = GetGValue(LColor);
	float b		 = GetBValue(LColor);
	auto  buffer = GetImageBuffer(mask);

	int maskRadius = int(Range);

	float realRadius = (Range / 2 - 35) - Radius;
	for (int y = 35; y < Range - 35; ++y) {
		for (int x = 35; x < Range - 35; ++x) {
			Vec2  point{static_cast<float>(x), static_cast<float>(y)};
			float distance = CircleSDF(point, center, Radius) / realRadius;
			if (distance < 0) {
				buffer[y * maskRadius + x] = BGR(LColor);
			} else if (distance <= 1) {
				auto rV = r * D(1 - distance);
				auto gV = g * D(1 - distance);
				auto bV = b * D(1 - distance);

				buffer[y * maskRadius + x] = RGB(bV, gV, rV);
			} else {
				buffer[y * maskRadius + x] = BLACK;
			}
		}
	}

	auto result = GassBlurFilter(mask, 30);
	delete mask;

	return result;
}

int main() {
	initgraph(BlockRadius * 5, BlockRadius * 5);

	BeginBatchDraw();

	InitAssets();

	HX::HXInitForEasyX();
	HX::SetBuffer(HX::GetHXBuffer(GetWorkingImage()));
	HX::WindowProfile windowProfile;

	auto generalMap = GeneratingMap();
	auto normalMap	= GeneratingNormalMap();
	auto blendedMap = Blend(generalMap, normalMap);
	auto redMask	= GeneratingLightMask(BlockRadius / 4, BlockRadius + 190, RGB(230, 32, 8));
	auto greenMask	= GeneratingLightMask(BlockRadius / 4, BlockRadius + 190, RGB(26, 174, 53));
	auto blueMask	= GeneratingLightMask(BlockRadius / 4, BlockRadius + 190, RGB(84, 214, 172));
	auto lightMask	= new IMAGE(BlockRadius * 5, BlockRadius * 5);

	float time		 = clock();
	auto  timer		 = clock();
	int	  fpsCounter = 0;
	int	  fps		 = 0;
	int	  count		 = 0;

	bool anime = false;
	while (true) {
		float iTime = (clock() - time) / 1000.f;
		if (!anime) {
			iTime = 0;
		}

		cleardevice();
		SetWorkingImage(lightMask);
		cleardevice();
		SetWorkingImage();

		SumOn(redMask, lightMask, BlockRadius + BlockRadius / 4 + 30 * sin(iTime),
			  BlockRadius * 2 + BlockRadius / 4 + 30 * cos(iTime));
		SumOn(greenMask, lightMask, BlockRadius * 2 + BlockRadius / 4 + 30 * sin(iTime),
			  BlockRadius / 4 + 30 * cos(iTime));
		SumOn(blueMask, lightMask, BlockRadius / 2 - BlockRadius / 4 + 30 * sin(iTime),
			  BlockRadius / 4 + 30 * cos(iTime));
		BlendOn(lightMask, blendedMap, nullptr);

		if (clock() - timer > 1000) {
			fps		   = fpsCounter;
			fpsCounter = 0;
			timer	   = clock();

			++count;
		}

		HX::HXBegin();

		ExMessage message;
		while (peekmessage(&message)) {
			HX::PushMessage(HX::GetHXMessage(&message));
		}

		HX::Window(HXStr("调试信息"), windowProfile);
		HX::Text( HXStr("FPS : ") + ToHXString(fps));
		HX::Text(std::format(HXStr("Red Light : ({}, {})"), BlockRadius + BlockRadius / 4 + 30 * sin(iTime), BlockRadius * 2 + BlockRadius / 4 + 30 * cos(iTime)));
		HX::Text(std::format(HXStr("Green Light : ({}, {})"), BlockRadius * 2 + BlockRadius / 4 + 30 * sin(iTime), BlockRadius / 4 + 30 * cos(iTime)));
		HX::Text(std::format(HXStr("Blue Light : ({}, {})"), BlockRadius / 2 - BlockRadius / 4 + 30 * sin(iTime), BlockRadius / 4 + 30 * cos(iTime)));

		static HX::ButtonProfile buttonProfile;
		if (anime) {
			if (HX::Button(HXStr("Stop"), buttonProfile)) {
				anime = false;
			}
		}
		else {
			if (HX::Button(HXStr("Resume"), buttonProfile)) {
				anime = true;
			}
		}

		HX::End();
		HX::Render();

		FlushBatchDraw();

		++fpsCounter;
	}

	_flushall();

	return 0;
}